Full source code website bán hàng thương mại điện tử gần giống shopee
472.077 lượt xem;
1 /*!
2 * jQuery Form Plugin
3 * version: 3.24 (26-DEC-2012)
4 * @requires jQuery v1.5 or later
5 *
6 * Examples and documentation at: http://malsup.com/jquery/form/
7 * Project repository: https://github.com/malsup/form
8 * Dual licensed under the MIT and GPL licenses:
9 * http://malsup.github.com/mit-license.txt
10 * http://malsup.github.com/gpl-license-v2.txt
11 */
12 /*global ActiveXObject alert */
13 ;(function($) {
14 "use strict";
15
16 /*
17 Usage Note:
18 -----------
19 Do not use both ajaxSubmit and ajaxForm on the same form. These
20 functions are mutually exclusive. Use ajaxSubmit if you want
21 to bind your own submit handler to the form. For example,
22
23 $(document).ready(function() {
24 $('#myForm').on('submit', function(e) {
25 e.preventDefault(); // <-- important
26 $(this).ajaxSubmit({
27 target: '#output'
28 });
29 });
30 });
31
32 Use ajaxForm when you want the plugin to manage all the event binding
33 for you. For example,
34
35 $(document).ready(function() {
36 $('#myForm').ajaxForm({
37 target: '#output'
38 });
39 });
40
41 You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
42 form does not have to exist when you invoke ajaxForm:
43
44 $('#myForm').ajaxForm({
45 delegation: true,
46 target: '#output'
47 });
48
49 When using ajaxForm, the ajaxSubmit function will be invoked for you
50 at the appropriate time.
51 */
52
53 /**
54 * Feature detection
55 */
56 var feature = {};
57 feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
58 feature.formdata = window.FormData !== undefined;
59
60 /**
61 * ajaxSubmit() provides a mechanism for immediately submitting
62 * an HTML form using AJAX.
63 */
64 $.fn.ajaxSubmit = function(options) {
65 /*jshint scripturl:true */
66
67 // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
68 if (!this.length) {
69 log('ajaxSubmit: skipping submit process - no element selected');
70 return this;
71 }
72
73 var method, action, url, $form = this;
74
75 if (typeof options == 'function') {
76 options = { success: options };
77 }
78
79 method = this.attr('method');
80 action = this.attr('action');
81 url = (typeof action === 'string') ? $.trim(action) : '';
82 url = url || window.location.href || '';
83 if (url) {
84 // clean url (don't include hash vaue)
85 url = (url.match(/^([^#]+)/)||[])[1];
86 }
87
88 options = $.extend(true, {
89 url: url,
90 success: $.ajaxSettings.success,
91 type: method || 'GET',
92 iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
93 }, options);
94
95 // hook for manipulating the form data before it is extracted;
96 // convenient for use with rich editors like tinyMCE or FCKEditor
97 var veto = {};
98 this.trigger('form-pre-serialize', [this, options, veto]);
99 if (veto.veto) {
100 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
101 return this;
102 }
103
104 // provide opportunity to alter form data before it is serialized
105 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
106 log('ajaxSubmit: submit aborted via beforeSerialize callback');
107 return this;
108 }
109
110 var traditional = options.traditional;
111 if ( traditional === undefined ) {
112 traditional = $.ajaxSettings.traditional;
113 }
114
115 var elements = [];
116 var qx, a = this.formToArray(options.semantic, elements);
117 if (options.data) {
118 options.extraData = options.data;
119 qx = $.param(options.data, traditional);
120 }
121
122 // give pre-submit callback an opportunity to abort the submit
123 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
124 log('ajaxSubmit: submit aborted via beforeSubmit callback');
125 return this;
126 }
127
128 // fire vetoable 'validate' event
129 this.trigger('form-submit-validate', [a, this, options, veto]);
130 if (veto.veto) {
131 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
132 return this;
133 }
134
135 var q = $.param(a, traditional);
136 if (qx) {
137 q = ( q ? (q + '&' + qx) : qx );
138 }
139 if (options.type.toUpperCase() == 'GET') {
140 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
141 options.data = null; // data is null for 'get'
142 }
143 else {
144 options.data = q; // data is the query string for 'post'
145 }
146
147 var callbacks = [];
148 if (options.resetForm) {
149 callbacks.push(function() { $form.resetForm(); });
150 }
151 if (options.clearForm) {
152 callbacks.push(function() { $form.clearForm(options.includeHidden); });
153 }
154
155 // perform a load on the target only if dataType is not provided
156 if (!options.dataType && options.target) {
157 var oldSuccess = options.success || function(){};
158 callbacks.push(function(data) {
159 var fn = options.replaceTarget ? 'replaceWith' : 'html';
160 $(options.target)[fn](data).each(oldSuccess, arguments);
161 });
162 }
163 else if (options.success) {
164 callbacks.push(options.success);
165 }
166
167 options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
168 var context = options.context || this ; // jQuery 1.4+ supports scope context
169 for (var i=0, max=callbacks.length; i < max; i++) {
170 callbacks[i].apply(context, [data, status, xhr || $form, $form]);
171 }
172 };
173
174 // are there files to upload?
175
176 // [value] (issue #113), also see comment:
177 // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
178 var fileInputs = $('input[type=file]:enabled[value!=""]', this);
179
180 var hasFileInputs = fileInputs.length > 0;
181 var mp = 'multipart/form-data';
182 var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
183
184 var fileAPI = feature.fileapi && feature.formdata;
185 log("fileAPI :" + fileAPI);
186 var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
187
188 var jqxhr;
189
190 // options.iframe allows user to force iframe mode
191 // 06-NOV-09: now defaulting to iframe mode if file input is detected
192 if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
193 // hack to fix Safari hang (thanks to Tim Molendijk for this)
194 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
195 if (options.closeKeepAlive) {
196 $.get(options.closeKeepAlive, function() {
197 jqxhr = fileUploadIframe(a);
198 });
199 }
200 else {
201 jqxhr = fileUploadIframe(a);
202 }
203 }
204 else if ((hasFileInputs || multipart) && fileAPI) {
205 jqxhr = fileUploadXhr(a);
206 }
207 else {
208 jqxhr = $.ajax(options);
209 }
210
211 $form.removeData('jqxhr').data('jqxhr', jqxhr);
212
213 // clear element array
214 for (var k=0; k < elements.length; k++)
215 elements[k] = null;
216
217 // fire 'notify' event
218 this.trigger('form-submit-notify', [this, options]);
219 return this;
220
221 // utility fn for deep serialization
222 function deepSerialize(extraData){
223 var serialized = $.param(extraData).split('&');
224 var len = serialized.length;
225 var result = {};
226 var i, part;
227 for (i=0; i < len; i++) {
228 // #252; undo param space replacement
229 serialized[i] = serialized[i].replace(/\+/g,' ');
230 part = serialized[i].split('=');
231 result[decodeURIComponent(part[0])] = decodeURIComponent(part[1]);
232 }
233 return result;
234 }
235
236 // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
237 function fileUploadXhr(a) {
238 var formdata = new FormData();
239
240 for (var i=0; i < a.length; i++) {
241 formdata.append(a[i].name, a[i].value);
242 }
243
244 if (options.extraData) {
245 var serializedData = deepSerialize(options.extraData);
246 for (var p in serializedData)
247 if (serializedData.hasOwnProperty(p))
248 formdata.append(p, serializedData[p]);
249 }
250
251 options.data = null;
252
253 var s = $.extend(true, {}, $.ajaxSettings, options, {
254 contentType: false,
255 processData: false,
256 cache: false,
257 type: method || 'POST'
258 });
259
260 if (options.uploadProgress) {
261 // workaround because jqXHR does not expose upload property
262 s.xhr = function() {
263 var xhr = jQuery.ajaxSettings.xhr();
264 if (xhr.upload) {
265 xhr.upload.onprogress = function(event) {
266 var percent = 0;
267 var position = event.loaded || event.position; /*event.position is deprecated*/
268 var total = event.total;
269 if (event.lengthComputable) {
270 percent = Math.ceil(position / total * 100);
271 }
272 options.uploadProgress(event, position, total, percent);
273 };
274 }
275 return xhr;
276 };
277 }
278
279 s.data = null;
280 var beforeSend = s.beforeSend;
281 s.beforeSend = function(xhr, o) {
282 o.data = formdata;
283 if(beforeSend)
284 beforeSend.call(this, xhr, o);
285 };
286 return $.ajax(s);
287 }
288
289 // private function for handling file uploads (hat tip to YAHOO!)
290 function fileUploadIframe(a) {
291 var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
292 var useProp = !!$.fn.prop;
293 var deferred = $.Deferred();
294
295 if ($('[name=submit],[id=submit]', form).length) {
296 // if there is an input with a name or id of 'submit' then we won't be
297 // able to invoke the submit fn on the form (at least not x-browser)
298 alert('Error: Form elements must not have name or id of "submit".');
299 deferred.reject();
300 return deferred;
301 }
302
303 if (a) {
304 // ensure that every serialized input is still enabled
305 for (i=0; i < elements.length; i++) {
306 el = $(elements[i]);
307 if ( useProp )
308 el.prop('disabled', false);
309 else
310 el.removeAttr('disabled');
311 }
312 }
313
314 s = $.extend(true, {}, $.ajaxSettings, options);
315 s.context = s.context || s;
316 id = 'jqFormIO' + (new Date().getTime());
317 if (s.iframeTarget) {
318 $io = $(s.iframeTarget);
319 n = $io.attr('name');
320 if (!n)
321 $io.attr('name', id);
322 else
323 id = n;
324 }
325 else {
326 $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
327 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
328 }
329 io = $io[0];
330
331
332 xhr = { // mock object
333 aborted: 0,
334 responseText: null,
335 responseXML: null,
336 status: 0,
337 statusText: 'n/a',
338 getAllResponseHeaders: function() {},
339 getResponseHeader: function() {},
340 setRequestHeader: function() {},
341 abort: function(status) {
342 var e = (status === 'timeout' ? 'timeout' : 'aborted');
343 log('aborting upload... ' + e);
344 this.aborted = 1;
345
346 try { // #214, #257
347 if (io.contentWindow.document.execCommand) {
348 io.contentWindow.document.execCommand('Stop');
349 }
350 }
351 catch(ignore) {}
352
353 $io.attr('src', s.iframeSrc); // abort op in progress
354 xhr.error = e;
355 if (s.error)
356 s.error.call(s.context, xhr, e, status);
357 if (g)
358 $.event.trigger("ajaxError", [xhr, s, e]);
359 if (s.complete)
360 s.complete.call(s.context, xhr, e);
361 }
362 };
363
364 g = s.global;
365 // trigger ajax global events so that activity/block indicators work like normal
366 if (g && 0 === $.active++) {
367 $.event.trigger("ajaxStart");
368 }
369 if (g) {
370 $.event.trigger("ajaxSend", [xhr, s]);
371 }
372
373 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
374 if (s.global) {
375 $.active--;
376 }
377 deferred.reject();
378 return deferred;
379 }
380 if (xhr.aborted) {
381 deferred.reject();
382 return deferred;
383 }
384
385 // add submitting element to data if we know it
386 sub = form.clk;
387 if (sub) {
388 n = sub.name;
389 if (n && !sub.disabled) {
390 s.extraData = s.extraData || {};
391 s.extraData[n] = sub.value;
392 if (sub.type == "image") {
393 s.extraData[n+'.x'] = form.clk_x;
394 s.extraData[n+'.y'] = form.clk_y;
395 }
396 }
397 }
398
399 var CLIENT_TIMEOUT_ABORT = 1;
400 var SERVER_ABORT = 2;
401
402 function getDoc(frame) {
403 var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
404 return doc;
405 }
406
407 // Rails CSRF hack (thanks to Yvan Barthelemy)
408 var csrf_token = $('meta[name=csrf-token]').attr('content');
409 var csrf_param = $('meta[name=csrf-param]').attr('content');
410 if (csrf_param && csrf_token) {
411 s.extraData = s.extraData || {};
412 s.extraData[csrf_param] = csrf_token;
413 }
414
415 // take a breath so that pending repaints get some cpu time before the upload starts
416 function doSubmit() {
417 // make sure form attrs are set
418 var t = $form.attr('target'), a = $form.attr('action');
419
420 // update form attrs in IE friendly way
421 form.setAttribute('target',id);
422 if (!method) {
423 form.setAttribute('method', 'POST');
424 }
425 if (a != s.url) {
426 form.setAttribute('action', s.url);
427 }
428
429 // ie borks in some cases when setting encoding
430 if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
431 $form.attr({
432 encoding: 'multipart/form-data',
433 enctype: 'multipart/form-data'
434 });
435 }
436
437 // support timout
438 if (s.timeout) {
439 timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
440 }
441
442 // look for server aborts
443 function checkState() {
444 try {
445 var state = getDoc(io).readyState;
446 log('state = ' + state);
447 if (state && state.toLowerCase() == 'uninitialized')
448 setTimeout(checkState,50);
449 }
450 catch(e) {
451 log('Server abort: ' , e, ' (', e.name, ')');
452 cb(SERVER_ABORT);
453 if (timeoutHandle)
454 clearTimeout(timeoutHandle);
455 timeoutHandle = undefined;
456 }
457 }
458
459 // add "extra" data to form if provided in options
460 var extraInputs = [];
461 try {
462 if (s.extraData) {
463 for (var n in s.extraData) {
464 if (s.extraData.hasOwnProperty(n)) {
465 // if using the $.param format that allows for multiple values with the same name
466 if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
467 extraInputs.push(
468 $('<input type="hidden" name="'+s.extraData[n].name+'">').val(s.extraData[n].value)
469 .appendTo(form)[0]);
470 } else {
471 extraInputs.push(
472 $('<input type="hidden" name="'+n+'">').val(s.extraData[n])
473 .appendTo(form)[0]);
474 }
475 }
476 }
477 }
478
479 if (!s.iframeTarget) {
480 // add iframe to doc and submit the form
481 $io.appendTo('body');
482 if (io.attachEvent)
483 io.attachEvent('onload', cb);
484 else
485 io.addEventListener('load', cb, false);
486 }
487 setTimeout(checkState,15);
488 form.submit();
489 }
490 finally {
491 // reset attrs and remove "extra" input elements
492 form.setAttribute('action',a);
493 if(t) {
494 form.setAttribute('target', t);
495 } else {
496 $form.removeAttr('target');
497 }
498 $(extraInputs).remove();
499 }
500 }
501
502 if (s.forceSync) {
503 doSubmit();
504 }
505 else {
506 setTimeout(doSubmit, 10); // this lets dom updates render
507 }
508
509 var data, doc, domCheckCount = 50, callbackProcessed;
510
511 function cb(e) {
512 if (xhr.aborted || callbackProcessed) {
513 return;
514 }
515 try {
516 doc = getDoc(io);
517 }
518 catch(ex) {
519 log('cannot access response document: ', ex);
520 e = SERVER_ABORT;
521 }
522 if (e === CLIENT_TIMEOUT_ABORT && xhr) {
523 xhr.abort('timeout');
524 deferred.reject(xhr, 'timeout');
525 return;
526 }
527 else if (e == SERVER_ABORT && xhr) {
528 xhr.abort('server abort');
529 deferred.reject(xhr, 'error', 'server abort');
530 return;
531 }
532
533 if (!doc || doc.location.href == s.iframeSrc) {
534 // response not received yet
535 if (!timedOut)
536 return;
537 }
538 if (io.detachEvent)
539 io.detachEvent('onload', cb);
540 else
541 io.removeEventListener('load', cb, false);
542
543 var status = 'success', errMsg;
544 try {
545 if (timedOut) {
546 throw 'timeout';
547 }
548
549 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
550 log('isXml='+isXml);
551 if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
552 if (--domCheckCount) {
553 // in some browsers (Opera) the iframe DOM is not always traversable when
554 // the onload callback fires, so we loop a bit to accommodate
555 log('requeing onLoad callback, DOM not available');
556 setTimeout(cb, 250);
557 return;
558 }
559 // let this fall through because server response could be an empty document
560 //log('Could not access iframe DOM after mutiple tries.');
561 //throw 'DOMException: not available';
562 }
563
564 //log('response detected');
565 var docRoot = doc.body ? doc.body : doc.documentElement;
566 xhr.responseText = docRoot ? docRoot.innerHTML : null;
567 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
568 if (isXml)
569 s.dataType = 'xml';
570 xhr.getResponseHeader = function(header){
571 var headers = {'content-type': s.dataType};
572 return headers[header];
573 };
574 // support for XHR 'status' & 'statusText' emulation :
575 if (docRoot) {
576 xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
577 xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
578 }
579
580 var dt = (s.dataType || '').toLowerCase();
581 var scr = /(json|script|text)/.test(dt);
582 if (scr || s.textarea) {
583 // see if user embedded response in textarea
584 var ta = doc.getElementsByTagName('textarea')[0];
585 if (ta) {
586 xhr.responseText = ta.value;
587 // support for XHR 'status' & 'statusText' emulation :
588 xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
589 xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
590 }
591 else if (scr) {
592 // account for browsers injecting pre around json response
593 var pre = doc.getElementsByTagName('pre')[0];
594 var b = doc.getElementsByTagName('body')[0];
595 if (pre) {
596 xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
597 }
598 else if (b) {
599 xhr.responseText = b.textContent ? b.textContent : b.innerText;
600 }
601 }
602 }
603 else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
604 xhr.responseXML = toXml(xhr.responseText);
605 }
606
607 try {
608 data = httpData(xhr, dt, s);
609 }
610 catch (e) {
611 status = 'parsererror';
612 xhr.error = errMsg = (e || status);
613 }
614 }
615 catch (e) {
616 log('error caught: ',e);
617 status = 'error';
618 xhr.error = errMsg = (e || status);
619 }
620
621 if (xhr.aborted) {
622 log('upload aborted');
623 status = null;
624 }
625
626 if (xhr.status) { // we've set xhr.status
627 status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
628 }
629
630 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
631 if (status === 'success') {
632 if (s.success)
633 s.success.call(s.context, data, 'success', xhr);
634 deferred.resolve(xhr.responseText, 'success', xhr);
635 if (g)
636 $.event.trigger("ajaxSuccess", [xhr, s]);
637 }
638 else if (status) {
639 if (errMsg === undefined)
640 errMsg = xhr.statusText;
641 if (s.error)
642 s.error.call(s.context, xhr, status, errMsg);
643 deferred.reject(xhr, 'error', errMsg);
644 if (g)
645 $.event.trigger("ajaxError", [xhr, s, errMsg]);
646 }
647
648 if (g)
649 $.event.trigger("ajaxComplete", [xhr, s]);
650
651 if (g && ! --$.active) {
652 $.event.trigger("ajaxStop");
653 }
654
655 if (s.complete)
656 s.complete.call(s.context, xhr, status);
657
658 callbackProcessed = true;
659 if (s.timeout)
660 clearTimeout(timeoutHandle);
661
662 // clean up
663 setTimeout(function() {
664 if (!s.iframeTarget)
665 $io.remove();
666 xhr.responseXML = null;
667 }, 100);
668 }
669
670 var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
671 if (window.ActiveXObject) {
672 doc = new ActiveXObject('Microsoft.XMLDOM');
673 doc.async = 'false';
674 doc.loadXML(s);
675 }
676 else {
677 doc = (new DOMParser()).parseFromString(s, 'text/xml');
678 }
679 return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
680 };
681 var parseJSON = $.parseJSON || function(s) {
682 /*jslint evil:true */
683 return window['eval']('(' + s + ')');
684 };
685
686 var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
687
688 var ct = xhr.getResponseHeader('content-type') || '',
689 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
690 data = xml ? xhr.responseXML : xhr.responseText;
691
692 if (xml && data.documentElement.nodeName === 'parsererror') {
693 if ($.error)
694 $.error('parsererror');
695 }
696 if (s && s.dataFilter) {
697 data = s.dataFilter(data, type);
698 }
699 if (typeof data === 'string') {
700 if (type === 'json' || !type && ct.indexOf('json') >= 0) {
701 data = parseJSON(data);
702 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
703 $.globalEval(data);
704 }
705 }
706 return data;
707 };
708
709 return deferred;
710 }
711 };
712
713 /**
714 * ajaxForm() provides a mechanism for fully automating form submission.
715 *
716 * The advantages of using this method instead of ajaxSubmit() are:
717 *
718 * 1: This method will include coordinates for <input type="image" /> elements (if the element
719 * is used to submit the form).
720 * 2. This method will include the submit element's name/value data (for the element that was
721 * used to submit the form).
722 * 3. This method binds the submit() method to the form for you.
723 *
724 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
725 * passes the options argument along after properly binding events for submit elements and
726 * the form itself.
727 */
728 $.fn.ajaxForm = function(options) {
729 options = options || {};
730 options.delegation = options.delegation && $.isFunction($.fn.on);
731
732 // in jQuery 1.3+ we can fix mistakes with the ready state
733 if (!options.delegation && this.length === 0) {
734 var o = { s: this.selector, c: this.context };
735 if (!$.isReady && o.s) {
736 log('DOM not ready, queuing ajaxForm');
737 $(function() {
738 $(o.s,o.c).ajaxForm(options);
739 });
740 return this;
741 }
742 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
743 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
744 return this;
745 }
746
747 if ( options.delegation ) {
748 $(document)
749 .off('submit.form-plugin', this.selector, doAjaxSubmit)
750 .off('click.form-plugin', this.selector, captureSubmittingElement)
751 .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
752 .on('click.form-plugin', this.selector, options, captureSubmittingElement);
753 return this;
754 }
755
756 return this.ajaxFormUnbind()
757 .bind('submit.form-plugin', options, doAjaxSubmit)
758 .bind('click.form-plugin', options, captureSubmittingElement);
759 };
760
761 // private event handlers
762 function doAjaxSubmit(e) {
763 /*jshint validthis:true */
764 var options = e.data;
765 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
766 e.preventDefault();
767 $(this).ajaxSubmit(options);
768 }
769 }
770
771 function captureSubmittingElement(e) {
772 /*jshint validthis:true */
773 var target = e.target;
774 var $el = $(target);
775 if (!($el.is("[type=submit],[type=image]"))) {
776 // is this a child element of the submit el? (ex: a span within a button)
777 var t = $el.closest('[type=submit]');
778 if (t.length === 0) {
779 return;
780 }
781 target = t[0];
782 }
783 var form = this;
784 form.clk = target;
785 if (target.type == 'image') {
786 if (e.offsetX !== undefined) {
787 form.clk_x = e.offsetX;
788 form.clk_y = e.offsetY;
789 } else if (typeof $.fn.offset == 'function') {
790 var offset = $el.offset();
791 form.clk_x = e.pageX - offset.left;
792 form.clk_y = e.pageY - offset.top;
793 } else {
794 form.clk_x = e.pageX - target.offsetLeft;
795 form.clk_y = e.pageY - target.offsetTop;
796 }
797 }
798 // clear form vars
799 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
800 }
801
802
803 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
804 $.fn.ajaxFormUnbind = function() {
805 return this.unbind('submit.form-plugin click.form-plugin');
806 };
807
808 /**
809 * formToArray() gathers form element data into an array of objects that can
810 * be passed to any of the following ajax functions: $.get, $.post, or load.
811 * Each object in the array has both a 'name' and 'value' property. An example of
812 * an array for a simple login form might be:
813 *
814 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
815 *
816 * It is this array that is passed to pre-submit callback functions provided to the
817 * ajaxSubmit() and ajaxForm() methods.
818 */
819 $.fn.formToArray = function(semantic, elements) {
820 var a = [];
821 if (this.length === 0) {
822 return a;
823 }
824
825 var form = this[0];
826 var els = semantic ? form.getElementsByTagName('*') : form.elements;
827 if (!els) {
828 return a;
829 }
830
831 var i,j,n,v,el,max,jmax;
832 for(i=0, max=els.length; i < max; i++) {
833 el = els[i];
834 n = el.name;
835 if (!n) {
836 continue;
837 }
838
839 if (semantic && form.clk && el.type == "image") {
840 // handle image inputs on the fly when semantic == true
841 if(!el.disabled && form.clk == el) {
842 a.push({name: n, value: $(el).val(), type: el.type });
843 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
844 }
845 continue;
846 }
847
848 v = $.fieldValue(el, true);
849 if (v && v.constructor == Array) {
850 if (elements)
851 elements.push(el);
852 for(j=0, jmax=v.length; j < jmax; j++) {
853 a.push({name: n, value: v[j]});
854 }
855 }
856 else if (feature.fileapi && el.type == 'file' && !el.disabled) {
857 if (elements)
858 elements.push(el);
859 var files = el.files;
860 if (files.length) {
861 for (j=0; j < files.length; j++) {
862 a.push({name: n, value: files[j], type: el.type});
863 }
864 }
865 else {
866 // #180
867 a.push({ name: n, value: '', type: el.type });
868 }
869 }
870 else if (v !== null && typeof v != 'undefined') {
871 if (elements)
872 elements.push(el);
873 a.push({name: n, value: v, type: el.type, required: el.required});
874 }
875 }
876
877 if (!semantic && form.clk) {
878 // input type=='image' are not found in elements array! handle it here
879 var $input = $(form.clk), input = $input[0];
880 n = input.name;
881 if (n && !input.disabled && input.type == 'image') {
882 a.push({name: n, value: $input.val()});
883 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
884 }
885 }
886 return a;
887 };
888
889 /**
890 * Serializes form data into a 'submittable' string. This method will return a string
891 * in the format: name1=value1&name2=value2
892 */
893 $.fn.formSerialize = function(semantic) {
894 //hand off to jQuery.param for proper encoding
895 return $.param(this.formToArray(semantic));
896 };
897
898 /**
899 * Serializes all field elements in the jQuery object into a query string.
900 * This method will return a string in the format: name1=value1&name2=value2
901 */
902 $.fn.fieldSerialize = function(successful) {
903 var a = [];
904 this.each(function() {
905 var n = this.name;
906 if (!n) {
907 return;
908 }
909 var v = $.fieldValue(this, successful);
910 if (v && v.constructor == Array) {
911 for (var i=0,max=v.length; i < max; i++) {
912 a.push({name: n, value: v[i]});
913 }
914 }
915 else if (v !== null && typeof v != 'undefined') {
916 a.push({name: this.name, value: v});
917 }
918 });
919 //hand off to jQuery.param for proper encoding
920 return $.param(a);
921 };
922
923 /**
924 * Returns the value(s) of the element in the matched set. For example, consider the following form:
925 *
926 * <form><fieldset>
927 * <input name="A" type="text" />
928 * <input name="A" type="text" />
929 * <input name="B" type="checkbox" value="B1" />
930 * <input name="B" type="checkbox" value="B2"/>
931 * <input name="C" type="radio" value="C1" />
932 * <input name="C" type="radio" value="C2" />
933 * </fieldset></form>
934 *
935 * var v = $('input[type=text]').fieldValue();
936 * // if no values are entered into the text inputs
937 * v == ['','']
938 * // if values entered into the text inputs are 'foo' and 'bar'
939 * v == ['foo','bar']
940 *
941 * var v = $('input[type=checkbox]').fieldValue();
942 * // if neither checkbox is checked
943 * v === undefined
944 * // if both checkboxes are checked
945 * v == ['B1', 'B2']
946 *
947 * var v = $('input[type=radio]').fieldValue();
948 * // if neither radio is checked
949 * v === undefined
950 * // if first radio is checked
951 * v == ['C1']
952 *
953 * The successful argument controls whether or not the field element must be 'successful'
954 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
955 * The default value of the successful argument is true. If this value is false the value(s)
956 * for each element is returned.
957 *
958 * Note: This method *always* returns an array. If no valid value can be determined the
959 * array will be empty, otherwise it will contain one or more values.
960 */
961 $.fn.fieldValue = function(successful) {
962 for (var val=[], i=0, max=this.length; i < max; i++) {
963 var el = this[i];
964 var v = $.fieldValue(el, successful);
965 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
966 continue;
967 }
968 if (v.constructor == Array)
969 $.merge(val, v);
970 else
971 val.push(v);
972 }
973 return val;
974 };
975
976 /**
977 * Returns the value of the field element.
978 */
979 $.fieldValue = function(el, successful) {
980 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
981 if (successful === undefined) {
982 successful = true;
983 }
984
985 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
986 (t == 'checkbox' || t == 'radio') && !el.checked ||
987 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
988 tag == 'select' && el.selectedIndex == -1)) {
989 return null;
990 }
991
992 if (tag == 'select') {
993 var index = el.selectedIndex;
994 if (index < 0) {
995 return null;
996 }
997 var a = [], ops = el.options;
998 var one = (t == 'select-one');
999 var max = (one ? index+1 : ops.length);
1000 for(var i=(one ? index : 0); i < max; i++) {
1001 var op = ops[i];
1002 if (op.selected) {
1003 var v = op.value;
1004 if (!v) { // extra pain for IE...
1005 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
1006 }
1007 if (one) {
1008 return v;
1009 }
1010 a.push(v);
1011 }
1012 }
1013 return a;
1014 }
1015 return $(el).val();
1016 };
1017
1018 /**
1019 * Clears the form data. Takes the following actions on the form's input fields:
1020 * - input text fields will have their 'value' property set to the empty string
1021 * - select elements will have their 'selectedIndex' property set to -1
1022 * - checkbox and radio inputs will have their 'checked' property set to false
1023 * - inputs of type submit, button, reset, and hidden will *not* be effected
1024 * - button elements will *not* be effected
1025 */
1026 $.fn.clearForm = function(includeHidden) {
1027 return this.each(function() {
1028 $('input,select,textarea', this).clearFields(includeHidden);
1029 });
1030 };
1031
1032 /**
1033 * Clears the selected form elements.
1034 */
1035 $.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
1036 var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
1037 return this.each(function() {
1038 var t = this.type, tag = this.tagName.toLowerCase();
1039 if (re.test(t) || tag == 'textarea') {
1040 this.value = '';
1041 }
1042 else if (t == 'checkbox' || t == 'radio') {
1043 this.checked = false;
1044 }
1045 else if (tag == 'select') {
1046 this.selectedIndex = -1;
1047 }
1048 else if (t == "file") {
1049 if ($.browser.msie) {
1050 $(this).replaceWith($(this).clone());
1051 } else {
1052 $(this).val('');
1053 }
1054 }
1055 else if (includeHidden) {
1056 // includeHidden can be the value true, or it can be a selector string
1057 // indicating a special test; for example:
1058 // $('#myForm').clearForm('.special:hidden')
1059 // the above would clean hidden inputs that have the class of 'special'
1060 if ( (includeHidden === true && /hidden/.test(t)) ||
1061 (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
1062 this.value = '';
1063 }
1064 });
1065 };
1066
1067 /**
1068 * Resets the form data. Causes all form elements to be reset to their original value.
1069 */
1070 $.fn.resetForm = function() {
1071 return this.each(function() {
1072 // guard against an input with the name of 'reset'
1073 // note that IE reports the reset function as an 'object'
1074 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
1075 this.reset();
1076 }
1077 });
1078 };
1079
1080 /**
1081 * Enables or disables any matching elements.
1082 */
1083 $.fn.enable = function(b) {
1084 if (b === undefined) {
1085 b = true;
1086 }
1087 return this.each(function() {
1088 this.disabled = !b;
1089 });
1090 };
1091
1092 /**
1093 * Checks/unchecks any matching checkboxes or radio buttons and
1094 * selects/deselects and matching option elements.
1095 */
1096 $.fn.selected = function(select) {
1097 if (select === undefined) {
1098 select = true;
1099 }
1100 return this.each(function() {
1101 var t = this.type;
1102 if (t == 'checkbox' || t == 'radio') {
1103 this.checked = select;
1104 }
1105 else if (this.tagName.toLowerCase() == 'option') {
1106 var $sel = $(this).parent('select');
1107 if (select && $sel[0] && $sel[0].type == 'select-one') {
1108 // deselect all other options
1109 $sel.find('option').selected(false);
1110 }
1111 this.selected = select;
1112 }
1113 });
1114 };
1115
1116 // expose debug var
1117 $.fn.ajaxSubmit.debug = false;
1118
1119 // helper fn for console logging
1120 function log() {
1121 if (!$.fn.ajaxSubmit.debug)
1122 return;
1123 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
1124 if (window.console && window.console.log) {
1125 window.console.log(msg);
1126 }
1127 else if (window.opera && window.opera.postError) {
1128 window.opera.postError(msg);
1129 }
1130 }
1131
1132 })(jQuery);
2 * jQuery Form Plugin
3 * version: 3.24 (26-DEC-2012)
4 * @requires jQuery v1.5 or later
5 *
6 * Examples and documentation at: http://malsup.com/jquery/form/
7 * Project repository: https://github.com/malsup/form
8 * Dual licensed under the MIT and GPL licenses:
9 * http://malsup.github.com/mit-license.txt
10 * http://malsup.github.com/gpl-license-v2.txt
11 */
12 /*global ActiveXObject alert */
13 ;(function($) {
14 "use strict";
15
16 /*
17 Usage Note:
18 -----------
19 Do not use both ajaxSubmit and ajaxForm on the same form. These
20 functions are mutually exclusive. Use ajaxSubmit if you want
21 to bind your own submit handler to the form. For example,
22
23 $(document).ready(function() {
24 $('#myForm').on('submit', function(e) {
25 e.preventDefault(); // <-- important
26 $(this).ajaxSubmit({
27 target: '#output'
28 });
29 });
30 });
31
32 Use ajaxForm when you want the plugin to manage all the event binding
33 for you. For example,
34
35 $(document).ready(function() {
36 $('#myForm').ajaxForm({
37 target: '#output'
38 });
39 });
40
41 You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
42 form does not have to exist when you invoke ajaxForm:
43
44 $('#myForm').ajaxForm({
45 delegation: true,
46 target: '#output'
47 });
48
49 When using ajaxForm, the ajaxSubmit function will be invoked for you
50 at the appropriate time.
51 */
52
53 /**
54 * Feature detection
55 */
56 var feature = {};
57 feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
58 feature.formdata = window.FormData !== undefined;
59
60 /**
61 * ajaxSubmit() provides a mechanism for immediately submitting
62 * an HTML form using AJAX.
63 */
64 $.fn.ajaxSubmit = function(options) {
65 /*jshint scripturl:true */
66
67 // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
68 if (!this.length) {
69 log('ajaxSubmit: skipping submit process - no element selected');
70 return this;
71 }
72
73 var method, action, url, $form = this;
74
75 if (typeof options == 'function') {
76 options = { success: options };
77 }
78
79 method = this.attr('method');
80 action = this.attr('action');
81 url = (typeof action === 'string') ? $.trim(action) : '';
82 url = url || window.location.href || '';
83 if (url) {
84 // clean url (don't include hash vaue)
85 url = (url.match(/^([^#]+)/)||[])[1];
86 }
87
88 options = $.extend(true, {
89 url: url,
90 success: $.ajaxSettings.success,
91 type: method || 'GET',
92 iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
93 }, options);
94
95 // hook for manipulating the form data before it is extracted;
96 // convenient for use with rich editors like tinyMCE or FCKEditor
97 var veto = {};
98 this.trigger('form-pre-serialize', [this, options, veto]);
99 if (veto.veto) {
100 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
101 return this;
102 }
103
104 // provide opportunity to alter form data before it is serialized
105 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
106 log('ajaxSubmit: submit aborted via beforeSerialize callback');
107 return this;
108 }
109
110 var traditional = options.traditional;
111 if ( traditional === undefined ) {
112 traditional = $.ajaxSettings.traditional;
113 }
114
115 var elements = [];
116 var qx, a = this.formToArray(options.semantic, elements);
117 if (options.data) {
118 options.extraData = options.data;
119 qx = $.param(options.data, traditional);
120 }
121
122 // give pre-submit callback an opportunity to abort the submit
123 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
124 log('ajaxSubmit: submit aborted via beforeSubmit callback');
125 return this;
126 }
127
128 // fire vetoable 'validate' event
129 this.trigger('form-submit-validate', [a, this, options, veto]);
130 if (veto.veto) {
131 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
132 return this;
133 }
134
135 var q = $.param(a, traditional);
136 if (qx) {
137 q = ( q ? (q + '&' + qx) : qx );
138 }
139 if (options.type.toUpperCase() == 'GET') {
140 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
141 options.data = null; // data is null for 'get'
142 }
143 else {
144 options.data = q; // data is the query string for 'post'
145 }
146
147 var callbacks = [];
148 if (options.resetForm) {
149 callbacks.push(function() { $form.resetForm(); });
150 }
151 if (options.clearForm) {
152 callbacks.push(function() { $form.clearForm(options.includeHidden); });
153 }
154
155 // perform a load on the target only if dataType is not provided
156 if (!options.dataType && options.target) {
157 var oldSuccess = options.success || function(){};
158 callbacks.push(function(data) {
159 var fn = options.replaceTarget ? 'replaceWith' : 'html';
160 $(options.target)[fn](data).each(oldSuccess, arguments);
161 });
162 }
163 else if (options.success) {
164 callbacks.push(options.success);
165 }
166
167 options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
168 var context = options.context || this ; // jQuery 1.4+ supports scope context
169 for (var i=0, max=callbacks.length; i < max; i++) {
170 callbacks[i].apply(context, [data, status, xhr || $form, $form]);
171 }
172 };
173
174 // are there files to upload?
175
176 // [value] (issue #113), also see comment:
177 // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
178 var fileInputs = $('input[type=file]:enabled[value!=""]', this);
179
180 var hasFileInputs = fileInputs.length > 0;
181 var mp = 'multipart/form-data';
182 var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
183
184 var fileAPI = feature.fileapi && feature.formdata;
185 log("fileAPI :" + fileAPI);
186 var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
187
188 var jqxhr;
189
190 // options.iframe allows user to force iframe mode
191 // 06-NOV-09: now defaulting to iframe mode if file input is detected
192 if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
193 // hack to fix Safari hang (thanks to Tim Molendijk for this)
194 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
195 if (options.closeKeepAlive) {
196 $.get(options.closeKeepAlive, function() {
197 jqxhr = fileUploadIframe(a);
198 });
199 }
200 else {
201 jqxhr = fileUploadIframe(a);
202 }
203 }
204 else if ((hasFileInputs || multipart) && fileAPI) {
205 jqxhr = fileUploadXhr(a);
206 }
207 else {
208 jqxhr = $.ajax(options);
209 }
210
211 $form.removeData('jqxhr').data('jqxhr', jqxhr);
212
213 // clear element array
214 for (var k=0; k < elements.length; k++)
215 elements[k] = null;
216
217 // fire 'notify' event
218 this.trigger('form-submit-notify', [this, options]);
219 return this;
220
221 // utility fn for deep serialization
222 function deepSerialize(extraData){
223 var serialized = $.param(extraData).split('&');
224 var len = serialized.length;
225 var result = {};
226 var i, part;
227 for (i=0; i < len; i++) {
228 // #252; undo param space replacement
229 serialized[i] = serialized[i].replace(/\+/g,' ');
230 part = serialized[i].split('=');
231 result[decodeURIComponent(part[0])] = decodeURIComponent(part[1]);
232 }
233 return result;
234 }
235
236 // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
237 function fileUploadXhr(a) {
238 var formdata = new FormData();
239
240 for (var i=0; i < a.length; i++) {
241 formdata.append(a[i].name, a[i].value);
242 }
243
244 if (options.extraData) {
245 var serializedData = deepSerialize(options.extraData);
246 for (var p in serializedData)
247 if (serializedData.hasOwnProperty(p))
248 formdata.append(p, serializedData[p]);
249 }
250
251 options.data = null;
252
253 var s = $.extend(true, {}, $.ajaxSettings, options, {
254 contentType: false,
255 processData: false,
256 cache: false,
257 type: method || 'POST'
258 });
259
260 if (options.uploadProgress) {
261 // workaround because jqXHR does not expose upload property
262 s.xhr = function() {
263 var xhr = jQuery.ajaxSettings.xhr();
264 if (xhr.upload) {
265 xhr.upload.onprogress = function(event) {
266 var percent = 0;
267 var position = event.loaded || event.position; /*event.position is deprecated*/
268 var total = event.total;
269 if (event.lengthComputable) {
270 percent = Math.ceil(position / total * 100);
271 }
272 options.uploadProgress(event, position, total, percent);
273 };
274 }
275 return xhr;
276 };
277 }
278
279 s.data = null;
280 var beforeSend = s.beforeSend;
281 s.beforeSend = function(xhr, o) {
282 o.data = formdata;
283 if(beforeSend)
284 beforeSend.call(this, xhr, o);
285 };
286 return $.ajax(s);
287 }
288
289 // private function for handling file uploads (hat tip to YAHOO!)
290 function fileUploadIframe(a) {
291 var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
292 var useProp = !!$.fn.prop;
293 var deferred = $.Deferred();
294
295 if ($('[name=submit],[id=submit]', form).length) {
296 // if there is an input with a name or id of 'submit' then we won't be
297 // able to invoke the submit fn on the form (at least not x-browser)
298 alert('Error: Form elements must not have name or id of "submit".');
299 deferred.reject();
300 return deferred;
301 }
302
303 if (a) {
304 // ensure that every serialized input is still enabled
305 for (i=0; i < elements.length; i++) {
306 el = $(elements[i]);
307 if ( useProp )
308 el.prop('disabled', false);
309 else
310 el.removeAttr('disabled');
311 }
312 }
313
314 s = $.extend(true, {}, $.ajaxSettings, options);
315 s.context = s.context || s;
316 id = 'jqFormIO' + (new Date().getTime());
317 if (s.iframeTarget) {
318 $io = $(s.iframeTarget);
319 n = $io.attr('name');
320 if (!n)
321 $io.attr('name', id);
322 else
323 id = n;
324 }
325 else {
326 $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
327 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
328 }
329 io = $io[0];
330
331
332 xhr = { // mock object
333 aborted: 0,
334 responseText: null,
335 responseXML: null,
336 status: 0,
337 statusText: 'n/a',
338 getAllResponseHeaders: function() {},
339 getResponseHeader: function() {},
340 setRequestHeader: function() {},
341 abort: function(status) {
342 var e = (status === 'timeout' ? 'timeout' : 'aborted');
343 log('aborting upload... ' + e);
344 this.aborted = 1;
345
346 try { // #214, #257
347 if (io.contentWindow.document.execCommand) {
348 io.contentWindow.document.execCommand('Stop');
349 }
350 }
351 catch(ignore) {}
352
353 $io.attr('src', s.iframeSrc); // abort op in progress
354 xhr.error = e;
355 if (s.error)
356 s.error.call(s.context, xhr, e, status);
357 if (g)
358 $.event.trigger("ajaxError", [xhr, s, e]);
359 if (s.complete)
360 s.complete.call(s.context, xhr, e);
361 }
362 };
363
364 g = s.global;
365 // trigger ajax global events so that activity/block indicators work like normal
366 if (g && 0 === $.active++) {
367 $.event.trigger("ajaxStart");
368 }
369 if (g) {
370 $.event.trigger("ajaxSend", [xhr, s]);
371 }
372
373 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
374 if (s.global) {
375 $.active--;
376 }
377 deferred.reject();
378 return deferred;
379 }
380 if (xhr.aborted) {
381 deferred.reject();
382 return deferred;
383 }
384
385 // add submitting element to data if we know it
386 sub = form.clk;
387 if (sub) {
388 n = sub.name;
389 if (n && !sub.disabled) {
390 s.extraData = s.extraData || {};
391 s.extraData[n] = sub.value;
392 if (sub.type == "image") {
393 s.extraData[n+'.x'] = form.clk_x;
394 s.extraData[n+'.y'] = form.clk_y;
395 }
396 }
397 }
398
399 var CLIENT_TIMEOUT_ABORT = 1;
400 var SERVER_ABORT = 2;
401
402 function getDoc(frame) {
403 var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
404 return doc;
405 }
406
407 // Rails CSRF hack (thanks to Yvan Barthelemy)
408 var csrf_token = $('meta[name=csrf-token]').attr('content');
409 var csrf_param = $('meta[name=csrf-param]').attr('content');
410 if (csrf_param && csrf_token) {
411 s.extraData = s.extraData || {};
412 s.extraData[csrf_param] = csrf_token;
413 }
414
415 // take a breath so that pending repaints get some cpu time before the upload starts
416 function doSubmit() {
417 // make sure form attrs are set
418 var t = $form.attr('target'), a = $form.attr('action');
419
420 // update form attrs in IE friendly way
421 form.setAttribute('target',id);
422 if (!method) {
423 form.setAttribute('method', 'POST');
424 }
425 if (a != s.url) {
426 form.setAttribute('action', s.url);
427 }
428
429 // ie borks in some cases when setting encoding
430 if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
431 $form.attr({
432 encoding: 'multipart/form-data',
433 enctype: 'multipart/form-data'
434 });
435 }
436
437 // support timout
438 if (s.timeout) {
439 timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
440 }
441
442 // look for server aborts
443 function checkState() {
444 try {
445 var state = getDoc(io).readyState;
446 log('state = ' + state);
447 if (state && state.toLowerCase() == 'uninitialized')
448 setTimeout(checkState,50);
449 }
450 catch(e) {
451 log('Server abort: ' , e, ' (', e.name, ')');
452 cb(SERVER_ABORT);
453 if (timeoutHandle)
454 clearTimeout(timeoutHandle);
455 timeoutHandle = undefined;
456 }
457 }
458
459 // add "extra" data to form if provided in options
460 var extraInputs = [];
461 try {
462 if (s.extraData) {
463 for (var n in s.extraData) {
464 if (s.extraData.hasOwnProperty(n)) {
465 // if using the $.param format that allows for multiple values with the same name
466 if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
467 extraInputs.push(
468 $('<input type="hidden" name="'+s.extraData[n].name+'">').val(s.extraData[n].value)
469 .appendTo(form)[0]);
470 } else {
471 extraInputs.push(
472 $('<input type="hidden" name="'+n+'">').val(s.extraData[n])
473 .appendTo(form)[0]);
474 }
475 }
476 }
477 }
478
479 if (!s.iframeTarget) {
480 // add iframe to doc and submit the form
481 $io.appendTo('body');
482 if (io.attachEvent)
483 io.attachEvent('onload', cb);
484 else
485 io.addEventListener('load', cb, false);
486 }
487 setTimeout(checkState,15);
488 form.submit();
489 }
490 finally {
491 // reset attrs and remove "extra" input elements
492 form.setAttribute('action',a);
493 if(t) {
494 form.setAttribute('target', t);
495 } else {
496 $form.removeAttr('target');
497 }
498 $(extraInputs).remove();
499 }
500 }
501
502 if (s.forceSync) {
503 doSubmit();
504 }
505 else {
506 setTimeout(doSubmit, 10); // this lets dom updates render
507 }
508
509 var data, doc, domCheckCount = 50, callbackProcessed;
510
511 function cb(e) {
512 if (xhr.aborted || callbackProcessed) {
513 return;
514 }
515 try {
516 doc = getDoc(io);
517 }
518 catch(ex) {
519 log('cannot access response document: ', ex);
520 e = SERVER_ABORT;
521 }
522 if (e === CLIENT_TIMEOUT_ABORT && xhr) {
523 xhr.abort('timeout');
524 deferred.reject(xhr, 'timeout');
525 return;
526 }
527 else if (e == SERVER_ABORT && xhr) {
528 xhr.abort('server abort');
529 deferred.reject(xhr, 'error', 'server abort');
530 return;
531 }
532
533 if (!doc || doc.location.href == s.iframeSrc) {
534 // response not received yet
535 if (!timedOut)
536 return;
537 }
538 if (io.detachEvent)
539 io.detachEvent('onload', cb);
540 else
541 io.removeEventListener('load', cb, false);
542
543 var status = 'success', errMsg;
544 try {
545 if (timedOut) {
546 throw 'timeout';
547 }
548
549 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
550 log('isXml='+isXml);
551 if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
552 if (--domCheckCount) {
553 // in some browsers (Opera) the iframe DOM is not always traversable when
554 // the onload callback fires, so we loop a bit to accommodate
555 log('requeing onLoad callback, DOM not available');
556 setTimeout(cb, 250);
557 return;
558 }
559 // let this fall through because server response could be an empty document
560 //log('Could not access iframe DOM after mutiple tries.');
561 //throw 'DOMException: not available';
562 }
563
564 //log('response detected');
565 var docRoot = doc.body ? doc.body : doc.documentElement;
566 xhr.responseText = docRoot ? docRoot.innerHTML : null;
567 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
568 if (isXml)
569 s.dataType = 'xml';
570 xhr.getResponseHeader = function(header){
571 var headers = {'content-type': s.dataType};
572 return headers[header];
573 };
574 // support for XHR 'status' & 'statusText' emulation :
575 if (docRoot) {
576 xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
577 xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
578 }
579
580 var dt = (s.dataType || '').toLowerCase();
581 var scr = /(json|script|text)/.test(dt);
582 if (scr || s.textarea) {
583 // see if user embedded response in textarea
584 var ta = doc.getElementsByTagName('textarea')[0];
585 if (ta) {
586 xhr.responseText = ta.value;
587 // support for XHR 'status' & 'statusText' emulation :
588 xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
589 xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
590 }
591 else if (scr) {
592 // account for browsers injecting pre around json response
593 var pre = doc.getElementsByTagName('pre')[0];
594 var b = doc.getElementsByTagName('body')[0];
595 if (pre) {
596 xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
597 }
598 else if (b) {
599 xhr.responseText = b.textContent ? b.textContent : b.innerText;
600 }
601 }
602 }
603 else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
604 xhr.responseXML = toXml(xhr.responseText);
605 }
606
607 try {
608 data = httpData(xhr, dt, s);
609 }
610 catch (e) {
611 status = 'parsererror';
612 xhr.error = errMsg = (e || status);
613 }
614 }
615 catch (e) {
616 log('error caught: ',e);
617 status = 'error';
618 xhr.error = errMsg = (e || status);
619 }
620
621 if (xhr.aborted) {
622 log('upload aborted');
623 status = null;
624 }
625
626 if (xhr.status) { // we've set xhr.status
627 status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
628 }
629
630 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
631 if (status === 'success') {
632 if (s.success)
633 s.success.call(s.context, data, 'success', xhr);
634 deferred.resolve(xhr.responseText, 'success', xhr);
635 if (g)
636 $.event.trigger("ajaxSuccess", [xhr, s]);
637 }
638 else if (status) {
639 if (errMsg === undefined)
640 errMsg = xhr.statusText;
641 if (s.error)
642 s.error.call(s.context, xhr, status, errMsg);
643 deferred.reject(xhr, 'error', errMsg);
644 if (g)
645 $.event.trigger("ajaxError", [xhr, s, errMsg]);
646 }
647
648 if (g)
649 $.event.trigger("ajaxComplete", [xhr, s]);
650
651 if (g && ! --$.active) {
652 $.event.trigger("ajaxStop");
653 }
654
655 if (s.complete)
656 s.complete.call(s.context, xhr, status);
657
658 callbackProcessed = true;
659 if (s.timeout)
660 clearTimeout(timeoutHandle);
661
662 // clean up
663 setTimeout(function() {
664 if (!s.iframeTarget)
665 $io.remove();
666 xhr.responseXML = null;
667 }, 100);
668 }
669
670 var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
671 if (window.ActiveXObject) {
672 doc = new ActiveXObject('Microsoft.XMLDOM');
673 doc.async = 'false';
674 doc.loadXML(s);
675 }
676 else {
677 doc = (new DOMParser()).parseFromString(s, 'text/xml');
678 }
679 return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
680 };
681 var parseJSON = $.parseJSON || function(s) {
682 /*jslint evil:true */
683 return window['eval']('(' + s + ')');
684 };
685
686 var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
687
688 var ct = xhr.getResponseHeader('content-type') || '',
689 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
690 data = xml ? xhr.responseXML : xhr.responseText;
691
692 if (xml && data.documentElement.nodeName === 'parsererror') {
693 if ($.error)
694 $.error('parsererror');
695 }
696 if (s && s.dataFilter) {
697 data = s.dataFilter(data, type);
698 }
699 if (typeof data === 'string') {
700 if (type === 'json' || !type && ct.indexOf('json') >= 0) {
701 data = parseJSON(data);
702 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
703 $.globalEval(data);
704 }
705 }
706 return data;
707 };
708
709 return deferred;
710 }
711 };
712
713 /**
714 * ajaxForm() provides a mechanism for fully automating form submission.
715 *
716 * The advantages of using this method instead of ajaxSubmit() are:
717 *
718 * 1: This method will include coordinates for <input type="image" /> elements (if the element
719 * is used to submit the form).
720 * 2. This method will include the submit element's name/value data (for the element that was
721 * used to submit the form).
722 * 3. This method binds the submit() method to the form for you.
723 *
724 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
725 * passes the options argument along after properly binding events for submit elements and
726 * the form itself.
727 */
728 $.fn.ajaxForm = function(options) {
729 options = options || {};
730 options.delegation = options.delegation && $.isFunction($.fn.on);
731
732 // in jQuery 1.3+ we can fix mistakes with the ready state
733 if (!options.delegation && this.length === 0) {
734 var o = { s: this.selector, c: this.context };
735 if (!$.isReady && o.s) {
736 log('DOM not ready, queuing ajaxForm');
737 $(function() {
738 $(o.s,o.c).ajaxForm(options);
739 });
740 return this;
741 }
742 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
743 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
744 return this;
745 }
746
747 if ( options.delegation ) {
748 $(document)
749 .off('submit.form-plugin', this.selector, doAjaxSubmit)
750 .off('click.form-plugin', this.selector, captureSubmittingElement)
751 .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
752 .on('click.form-plugin', this.selector, options, captureSubmittingElement);
753 return this;
754 }
755
756 return this.ajaxFormUnbind()
757 .bind('submit.form-plugin', options, doAjaxSubmit)
758 .bind('click.form-plugin', options, captureSubmittingElement);
759 };
760
761 // private event handlers
762 function doAjaxSubmit(e) {
763 /*jshint validthis:true */
764 var options = e.data;
765 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
766 e.preventDefault();
767 $(this).ajaxSubmit(options);
768 }
769 }
770
771 function captureSubmittingElement(e) {
772 /*jshint validthis:true */
773 var target = e.target;
774 var $el = $(target);
775 if (!($el.is("[type=submit],[type=image]"))) {
776 // is this a child element of the submit el? (ex: a span within a button)
777 var t = $el.closest('[type=submit]');
778 if (t.length === 0) {
779 return;
780 }
781 target = t[0];
782 }
783 var form = this;
784 form.clk = target;
785 if (target.type == 'image') {
786 if (e.offsetX !== undefined) {
787 form.clk_x = e.offsetX;
788 form.clk_y = e.offsetY;
789 } else if (typeof $.fn.offset == 'function') {
790 var offset = $el.offset();
791 form.clk_x = e.pageX - offset.left;
792 form.clk_y = e.pageY - offset.top;
793 } else {
794 form.clk_x = e.pageX - target.offsetLeft;
795 form.clk_y = e.pageY - target.offsetTop;
796 }
797 }
798 // clear form vars
799 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
800 }
801
802
803 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
804 $.fn.ajaxFormUnbind = function() {
805 return this.unbind('submit.form-plugin click.form-plugin');
806 };
807
808 /**
809 * formToArray() gathers form element data into an array of objects that can
810 * be passed to any of the following ajax functions: $.get, $.post, or load.
811 * Each object in the array has both a 'name' and 'value' property. An example of
812 * an array for a simple login form might be:
813 *
814 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
815 *
816 * It is this array that is passed to pre-submit callback functions provided to the
817 * ajaxSubmit() and ajaxForm() methods.
818 */
819 $.fn.formToArray = function(semantic, elements) {
820 var a = [];
821 if (this.length === 0) {
822 return a;
823 }
824
825 var form = this[0];
826 var els = semantic ? form.getElementsByTagName('*') : form.elements;
827 if (!els) {
828 return a;
829 }
830
831 var i,j,n,v,el,max,jmax;
832 for(i=0, max=els.length; i < max; i++) {
833 el = els[i];
834 n = el.name;
835 if (!n) {
836 continue;
837 }
838
839 if (semantic && form.clk && el.type == "image") {
840 // handle image inputs on the fly when semantic == true
841 if(!el.disabled && form.clk == el) {
842 a.push({name: n, value: $(el).val(), type: el.type });
843 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
844 }
845 continue;
846 }
847
848 v = $.fieldValue(el, true);
849 if (v && v.constructor == Array) {
850 if (elements)
851 elements.push(el);
852 for(j=0, jmax=v.length; j < jmax; j++) {
853 a.push({name: n, value: v[j]});
854 }
855 }
856 else if (feature.fileapi && el.type == 'file' && !el.disabled) {
857 if (elements)
858 elements.push(el);
859 var files = el.files;
860 if (files.length) {
861 for (j=0; j < files.length; j++) {
862 a.push({name: n, value: files[j], type: el.type});
863 }
864 }
865 else {
866 // #180
867 a.push({ name: n, value: '', type: el.type });
868 }
869 }
870 else if (v !== null && typeof v != 'undefined') {
871 if (elements)
872 elements.push(el);
873 a.push({name: n, value: v, type: el.type, required: el.required});
874 }
875 }
876
877 if (!semantic && form.clk) {
878 // input type=='image' are not found in elements array! handle it here
879 var $input = $(form.clk), input = $input[0];
880 n = input.name;
881 if (n && !input.disabled && input.type == 'image') {
882 a.push({name: n, value: $input.val()});
883 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
884 }
885 }
886 return a;
887 };
888
889 /**
890 * Serializes form data into a 'submittable' string. This method will return a string
891 * in the format: name1=value1&name2=value2
892 */
893 $.fn.formSerialize = function(semantic) {
894 //hand off to jQuery.param for proper encoding
895 return $.param(this.formToArray(semantic));
896 };
897
898 /**
899 * Serializes all field elements in the jQuery object into a query string.
900 * This method will return a string in the format: name1=value1&name2=value2
901 */
902 $.fn.fieldSerialize = function(successful) {
903 var a = [];
904 this.each(function() {
905 var n = this.name;
906 if (!n) {
907 return;
908 }
909 var v = $.fieldValue(this, successful);
910 if (v && v.constructor == Array) {
911 for (var i=0,max=v.length; i < max; i++) {
912 a.push({name: n, value: v[i]});
913 }
914 }
915 else if (v !== null && typeof v != 'undefined') {
916 a.push({name: this.name, value: v});
917 }
918 });
919 //hand off to jQuery.param for proper encoding
920 return $.param(a);
921 };
922
923 /**
924 * Returns the value(s) of the element in the matched set. For example, consider the following form:
925 *
926 * <form><fieldset>
927 * <input name="A" type="text" />
928 * <input name="A" type="text" />
929 * <input name="B" type="checkbox" value="B1" />
930 * <input name="B" type="checkbox" value="B2"/>
931 * <input name="C" type="radio" value="C1" />
932 * <input name="C" type="radio" value="C2" />
933 * </fieldset></form>
934 *
935 * var v = $('input[type=text]').fieldValue();
936 * // if no values are entered into the text inputs
937 * v == ['','']
938 * // if values entered into the text inputs are 'foo' and 'bar'
939 * v == ['foo','bar']
940 *
941 * var v = $('input[type=checkbox]').fieldValue();
942 * // if neither checkbox is checked
943 * v === undefined
944 * // if both checkboxes are checked
945 * v == ['B1', 'B2']
946 *
947 * var v = $('input[type=radio]').fieldValue();
948 * // if neither radio is checked
949 * v === undefined
950 * // if first radio is checked
951 * v == ['C1']
952 *
953 * The successful argument controls whether or not the field element must be 'successful'
954 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
955 * The default value of the successful argument is true. If this value is false the value(s)
956 * for each element is returned.
957 *
958 * Note: This method *always* returns an array. If no valid value can be determined the
959 * array will be empty, otherwise it will contain one or more values.
960 */
961 $.fn.fieldValue = function(successful) {
962 for (var val=[], i=0, max=this.length; i < max; i++) {
963 var el = this[i];
964 var v = $.fieldValue(el, successful);
965 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
966 continue;
967 }
968 if (v.constructor == Array)
969 $.merge(val, v);
970 else
971 val.push(v);
972 }
973 return val;
974 };
975
976 /**
977 * Returns the value of the field element.
978 */
979 $.fieldValue = function(el, successful) {
980 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
981 if (successful === undefined) {
982 successful = true;
983 }
984
985 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
986 (t == 'checkbox' || t == 'radio') && !el.checked ||
987 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
988 tag == 'select' && el.selectedIndex == -1)) {
989 return null;
990 }
991
992 if (tag == 'select') {
993 var index = el.selectedIndex;
994 if (index < 0) {
995 return null;
996 }
997 var a = [], ops = el.options;
998 var one = (t == 'select-one');
999 var max = (one ? index+1 : ops.length);
1000 for(var i=(one ? index : 0); i < max; i++) {
1001 var op = ops[i];
1002 if (op.selected) {
1003 var v = op.value;
1004 if (!v) { // extra pain for IE...
1005 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
1006 }
1007 if (one) {
1008 return v;
1009 }
1010 a.push(v);
1011 }
1012 }
1013 return a;
1014 }
1015 return $(el).val();
1016 };
1017
1018 /**
1019 * Clears the form data. Takes the following actions on the form's input fields:
1020 * - input text fields will have their 'value' property set to the empty string
1021 * - select elements will have their 'selectedIndex' property set to -1
1022 * - checkbox and radio inputs will have their 'checked' property set to false
1023 * - inputs of type submit, button, reset, and hidden will *not* be effected
1024 * - button elements will *not* be effected
1025 */
1026 $.fn.clearForm = function(includeHidden) {
1027 return this.each(function() {
1028 $('input,select,textarea', this).clearFields(includeHidden);
1029 });
1030 };
1031
1032 /**
1033 * Clears the selected form elements.
1034 */
1035 $.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
1036 var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
1037 return this.each(function() {
1038 var t = this.type, tag = this.tagName.toLowerCase();
1039 if (re.test(t) || tag == 'textarea') {
1040 this.value = '';
1041 }
1042 else if (t == 'checkbox' || t == 'radio') {
1043 this.checked = false;
1044 }
1045 else if (tag == 'select') {
1046 this.selectedIndex = -1;
1047 }
1048 else if (t == "file") {
1049 if ($.browser.msie) {
1050 $(this).replaceWith($(this).clone());
1051 } else {
1052 $(this).val('');
1053 }
1054 }
1055 else if (includeHidden) {
1056 // includeHidden can be the value true, or it can be a selector string
1057 // indicating a special test; for example:
1058 // $('#myForm').clearForm('.special:hidden')
1059 // the above would clean hidden inputs that have the class of 'special'
1060 if ( (includeHidden === true && /hidden/.test(t)) ||
1061 (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
1062 this.value = '';
1063 }
1064 });
1065 };
1066
1067 /**
1068 * Resets the form data. Causes all form elements to be reset to their original value.
1069 */
1070 $.fn.resetForm = function() {
1071 return this.each(function() {
1072 // guard against an input with the name of 'reset'
1073 // note that IE reports the reset function as an 'object'
1074 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
1075 this.reset();
1076 }
1077 });
1078 };
1079
1080 /**
1081 * Enables or disables any matching elements.
1082 */
1083 $.fn.enable = function(b) {
1084 if (b === undefined) {
1085 b = true;
1086 }
1087 return this.each(function() {
1088 this.disabled = !b;
1089 });
1090 };
1091
1092 /**
1093 * Checks/unchecks any matching checkboxes or radio buttons and
1094 * selects/deselects and matching option elements.
1095 */
1096 $.fn.selected = function(select) {
1097 if (select === undefined) {
1098 select = true;
1099 }
1100 return this.each(function() {
1101 var t = this.type;
1102 if (t == 'checkbox' || t == 'radio') {
1103 this.checked = select;
1104 }
1105 else if (this.tagName.toLowerCase() == 'option') {
1106 var $sel = $(this).parent('select');
1107 if (select && $sel[0] && $sel[0].type == 'select-one') {
1108 // deselect all other options
1109 $sel.find('option').selected(false);
1110 }
1111 this.selected = select;
1112 }
1113 });
1114 };
1115
1116 // expose debug var
1117 $.fn.ajaxSubmit.debug = false;
1118
1119 // helper fn for console logging
1120 function log() {
1121 if (!$.fn.ajaxSubmit.debug)
1122 return;
1123 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
1124 if (window.console && window.console.log) {
1125 window.console.log(msg);
1126 }
1127 else if (window.opera && window.opera.postError) {
1128 window.opera.postError(msg);
1129 }
1130 }
1131
1132 })(jQuery);